﻿namespace JoinBox.RemoteAccess;

using IFoxCAD.LoadEx;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Resources;
using System.Runtime.Remoting;

// 参考文章1 https://www.cnblogs.com/zlgcool/archive/2008/10/12/1309616.html
// 参考文章2 https://www.cnblogs.com/roucheng/p/csdongtai.html

[Serializable]
public class AssemblyInfo
{
    public Assembly? Assembly;
    public string? Namespace;
    public string? Class;
    public string? Method;
    public string? TypeFullName;
}

/// <summary>
/// 通讯代理类
/// </summary>
[Serializable]
public class RemoteLoader : MarshalByRefObject
{
    #region 成员
    /// <summary>
    /// 加载的链条
    /// </summary>
    List<LoadState> _MyLoadAssemblys;
    /// <summary>
    /// 链式加载器
    /// </summary>
    AssemblyDependent _AssemblyDependent;
    /// <summary>
    /// 链条头的dll加载成功
    /// </summary>
    public bool LoadDllOK;
    /// <summary>
    /// 加载的错误信息,可以获取到链条中段的错误
    /// </summary>
    public string? LoadMessage;
    #endregion

    public RemoteLoader()
    {
        _AssemblyDependent = new AssemblyDependent();
        _MyLoadAssemblys = new();
    }

    /// <summary>
    /// 域内进行链式加载dll
    /// </summary>
    /// <param name="file"></param>
    public void LoadAssembly(string sFile)
    {
        LoadDllOK = _AssemblyDependent.Load(sFile, _MyLoadAssemblys);
        LoadMessage = AssemblyDependent.PrintMessage(_MyLoadAssemblys);
    }

    /// <summary>
    /// 加载cad的东西只能在外面做,
    /// 而且远程通讯方法又必须在MarshalByRefObject接口下,
    /// 所以这提供遍历加载的程序集们方法
    /// </summary>
    /// <param name="ac"><see cref="RemoteLoader"/>封送本类|
    /// <see cref="Assembly"/>封送程序集|
    /// <see cref="object[]"/>封送参数|
    /// <see cref="object"/>封送返回值,<see cref="!null"/>结束循环.
    /// </param>
    /// <param name="ars">外参数传入封送接口</param>
    public object? TraversideLoadAssemblys(Func<RemoteLoader, Assembly, object[], object?> ac, object[]? ars = null)
    {
        if (_AssemblyDependent is null)
            return null;
        ars ??= new object[] { };
        object? result = null;
        foreach (var assembly in _MyLoadAssemblys)
        {
            if (assembly.Assembly == null)
                continue;
            result = ac.Invoke(this, assembly.Assembly, ars);
            if (result is not null)
                break;
        }
        return result;
    }

    /// <summary>
    /// 遍历出"方法"在链条中什么dll上,返回程序集
    /// </summary>
    /// <param name="className">命名空间+类名</param>
    /// <param name="methodName">方法名</param>
    /// <param name="typeFullName">返回类型名</param>
    /// <returns>程序集</returns>
    AssemblyInfo? GetAssembly(string className, string methodName)
    {
        var ta = this.TraversideLoadAssemblys((remoteLoaderFactory, assembly, ars) => {

            // 获取所有类型
            var types = assembly.GetTypes();
            foreach (var type in types)
            {
                if (!type.IsClass || !type.IsPublic)
                    continue;
                if (type.FullName != className) // type.Namespace + "." + type.Name != className
                    continue;

                foreach (var method in type.GetMethods())
                {
                    if (!method.IsPublic)
                        continue;
                    // cad可以用这个,属性名称
                    // if (method.GetCustomAttributes(true).Length == 0)
                    // {
                    //    continue;
                    // }
                    // 转大写匹配命令
                    if (method.Name.ToUpper() == methodName.ToUpper())
                    {
                        var asInfo = new AssemblyInfo
                        {
                            TypeFullName = type.FullName,
                            Assembly = assembly,
                            Namespace = type.Namespace,
                            Class = type.Name,
                            Method = method.Name,
                        };
                        return asInfo;
                    }
                }
            }
            return null;
        });

        if (ta is null)
            return null;
        return (AssemblyInfo)ta;
    }

    /// <summary>
    /// 调用载入的dll的方法(会自动找程序集)
    /// </summary>
    /// <param name="spaceClass">命名空间+类名</param>
    /// <param name="methodName">方法名</param>
    /// <param name="args">方法需要的参数</param>
    /// <returns></returns>
    public object? Invoke(string spaceClass, string methodName, params object[] args)
    {
        if (_AssemblyDependent is null)
            throw new ArgumentNullException(nameof(_AssemblyDependent));

        if (_MyLoadAssemblys.Count < 1)
            throw new ArgumentNullException("没构造或加载失败");

        var assemblyInfo = GetAssembly(spaceClass, methodName);
        if (assemblyInfo is null)
            throw new NotSupportedException("找不到指定的命名空间和类名:" + spaceClass);
        return Invoke(assemblyInfo.Assembly, assemblyInfo.Namespace + "." + assemblyInfo.Class, assemblyInfo.Method, args);
    }

    /// <summary>
    /// 调用载入的dll的方法
    /// </summary>
    /// <param name="assembly">程序集</param>
    /// <param name="spaceClass">命名空间+类名</param>
    /// <param name="methodName">方法名</param>
    /// <param name="args">方法需要的参数</param>
    /// <returns></returns>
    public object? Invoke(Assembly? assembly, string? spaceClass, string? methodName, params object[]? args)
    {
        if (assembly is null)
            throw new ArgumentException("没程序域");
        if (spaceClass is null)
            throw new ArgumentException(nameof(spaceClass));
        if (methodName is null)
            throw new ArgumentException(nameof(methodName));

        Type spaceClassType = assembly.GetType(spaceClass);
        if (spaceClassType is null)
            throw new ArgumentException("命名空间.类型出错");

        // 转大写匹配命令(如果是方法的话,这里可能有重载)
        List<MethodInfo> methodInfos = new();
        foreach (var item in spaceClassType.GetMethods())
        {
            if (item.Name.ToUpper() == methodName.ToUpper())
                methodInfos.Add(item);
        }
        if (methodInfos.Count == 0)
            throw new ArgumentNullException("方法出错");

        var spaceClassInstance = Activator.CreateInstance(spaceClassType);

        object? result = null;
        foreach (var methodInfo in methodInfos)
        {
            try
            {
                // 此句若出错表示运行域不在准确的域内,要去检查一下,此句也会导致GC释放出错
                // 没有参数
                if (args is null)
                    result = methodInfo.Invoke(spaceClassInstance, new object[] { });
                else
                    result = methodInfo.Invoke(spaceClassInstance, args);

                // 参数1,重载
                // result = methodInfo.Invoke(spaceClassInstance, new object[] { "fsdfasfasf" });
            }
            catch
            { }
        }
        // 调用方式改变(但是这个方法需要 Assembly.LoadForm 否则无法查找影像文件)
        // return Activator.CreateInstanceFrom(assembly.FullName, spaceClass + "." + methodName, false, bfi, null, args, null, null, null).Unwrap();
        return result;// 只需要返回循环最后那个.
    }
}